﻿/**************************************************************
 *
 * 唯一标识：772e1481-accf-462e-abce-7dd64b1955c4
 * 命名空间：Sgr.Identity.Services
 * 创建时间：2023/8/28 9:17:42
 * 机器名称：DESKTOP-S0D075D
 * 创建者：antho
 * 电子邮箱：fengqinhua2016@163.com
 * 描述：
 *
 **************************************************************/

using Microsoft.Extensions.Options;
using Sgr.Domain.Entities;
using Sgr.Exceptions;
using Sgr.Security.VerifyCode;
using Sgr.UPMS;
using Sgr.UPMS.Domain.LogLogins;
using Sgr.UPMS.Domain.Users;
using Sgr.UPMS.Domain.UserTokens;
using Sgr.Utilities;

namespace Sgr.Identity.Services
{
    public class UpmsAccountAuthService : IAccountAuthService
    {
        private readonly ILogLoginRepository _logLoginRepository;
        private readonly IUserRefreshTokenRepository _userRefreshTokenRepository;
        private readonly IUserRepository _userRepository;
        private readonly IPasswordHashService _passwordHashService;
        private readonly UpmsOptions _upmsOptions;

        public UpmsAccountAuthService(IUserRepository userRepository,
            ILogLoginRepository logLoginRepository,
            IUserRefreshTokenRepository userRefreshTokenRepository,
            IPasswordHashService passwordHashService,
            IOptions<UpmsOptions> options)
        {
            _userRefreshTokenRepository = userRefreshTokenRepository;
            _userRepository = userRepository;
            _passwordHashService = passwordHashService;
            _upmsOptions = options.Value;
            _logLoginRepository = logLoginRepository;
        }

        /// <summary>
        /// 创建刷新Token
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="newRefreshToken">新令牌值</param>
        /// <param name="oldRefreshToken">旧令牌值（如果不存在则为空字符串）</param>
        /// <param name="minutes">刷新令牌有效时长（分钟）,默认12小时</param>
        /// <param name="bindingMark">与令牌的绑定用户的设备信息或会话标识符</param>
        /// <param name="cancellationToken"></param>
        public async Task CreateRefreshTokenAsync(string userId, string newRefreshToken, string oldRefreshToken = "", int minutes = 720, string bindingMark = "", CancellationToken cancellationToken = default)
        {
            if (!long.TryParse(userId, out long id))
                throw new BusinessException($"userId {userId} 格式错误");

            //创建新的刷新令牌
            var expires = DateTime.UtcNow.AddMinutes(minutes);
            var userRefreshToken = new UserRefreshToken(newRefreshToken, expires, id, bindingMark);
            await _userRefreshTokenRepository.InsertAsync(userRefreshToken, cancellationToken);

            if (!string.IsNullOrEmpty(oldRefreshToken))
            {
                // 如果存在就的刷新令牌，则删除它 [此时使用刷新令牌更新访问令牌]
                UserRefreshToken? oldUserRefreshToken = await _userRefreshTokenRepository.GetByRefreshTokenAsync(oldRefreshToken, cancellationToken);
                if (oldUserRefreshToken != null)
                    await _userRefreshTokenRepository.DeleteAsync(oldUserRefreshToken, cancellationToken);
            }
            else
            {
                // 如果不存在旧的刷新令牌，则删除所有旧的刷新令牌 [此时正常登录]
                await _userRefreshTokenRepository.RemoveExpiredByUserIdAsync(id, cancellationToken);
            }
        }

        /// <summary>
        /// 执行登录（直接使用手机号码或邮箱登录）
        /// </summary>
        /// <param name="receiverType"></param>
        /// <param name="receiver"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task<Tuple<AccountLoginResults, Account?>> ValidateAccountAsync(ReceiverType receiverType, string receiver, CancellationToken cancellationToken = default)
        {
            // 根据接收类型获取用户
            User? user = null;
            if (receiverType == ReceiverType.Phone)
                user = await _userRepository.GetByPhoneAsync(receiver, cancellationToken);
            else if (receiverType == ReceiverType.Email)
                user = await _userRepository.GetByEmailAsync(receiver, cancellationToken);

            if (user == null)
                return new Tuple<AccountLoginResults, Account?>(AccountLoginResults.NotExist, null);

            // 使用共享方法处理用户状态检查和登录成功逻辑
            return await ProcessAccountValidationAsync(user, true, cancellationToken);
        }

        /// <summary>
        /// 检查账号是否已注册
        /// </summary>
        /// <param name="receiverType"></param>
        /// <param name="receiver"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task<bool> IsAccountRegistered(ReceiverType receiverType, string receiver, CancellationToken cancellationToken = default)
        {
            if (receiverType == ReceiverType.Phone)
                return !await _userRepository.PhoneNumberIsUniqueAsync(receiver, null, cancellationToken);
            else if (receiverType == ReceiverType.Email)
                return !await _userRepository.EmailIsUniqueAsync(receiver, null, cancellationToken);
            return false;
        }

        /// <summary>
        /// 执行登录（验证账号和密码是否正确）
        /// </summary>
        /// <param name="loginName"></param>
        /// <param name="password"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task<Tuple<AccountLoginResults, Account?>> ValidateAccountAsync(string loginName, string password, CancellationToken cancellationToken = default)
        {
            // 通过登录名获取用户
            User? user = await _userRepository.GetByLoginNameAsync(loginName, cancellationToken);

            if (user == null && RegexHelper.IsValidPhoneNumber(loginName))
            {
                user = await _userRepository.GetByPhoneAsync(loginName, cancellationToken);
            }

            if (user == null && RegexHelper.IsValidPhoneNumber(loginName))
            {
                user = await _userRepository.GetByEmailAsync(loginName, cancellationToken);
            }

            if (user == null)
            {
                return new Tuple<AccountLoginResults, Account?>(AccountLoginResults.NotExist, null);
            }

            // 验证密码
            bool passwordValid = user.CheckPassword(_passwordHashService, password);

            // 使用共享方法处理用户状态检查和登录结果逻辑
            return await ProcessAccountValidationAsync(user, passwordValid, cancellationToken);
        }

        /// <summary>
        /// 处理账号验证的共享逻辑
        /// </summary>
        /// <param name="user">用户对象</param>
        /// <param name="isValid">验证是否通过（密码验证或验证码验证）</param>
        /// <param name="cancellationToken">取消令牌</param>
        /// <returns>登录结果和账号信息的元组</returns>
        private async Task<Tuple<AccountLoginResults, Account?>> ProcessAccountValidationAsync(
            User user,
            bool isValid,
            CancellationToken cancellationToken)
        {
            // 检查用户状态
            UserState status = user.GetUserState();

            // 如果用户状态不正常，根据状态返回相应的结果
            if (status != UserState.Normal)
            {
                return status switch
                {
                    UserState.Deleted => new Tuple<AccountLoginResults, Account?>(AccountLoginResults.IsDelete, null),
                    UserState.Disabled => new Tuple<AccountLoginResults, Account?>(AccountLoginResults.IsDeactivate, null),
                    UserState.Locked => new Tuple<AccountLoginResults, Account?>(AccountLoginResults.IsLock, null),
                    _ => new Tuple<AccountLoginResults, Account?>(AccountLoginResults.IsDeactivate, null)
                };
            }

            // 根据验证结果处理
            if (isValid)
            {
                // 登录成功
                user.LoginSuccess();
                await _userRepository.UpdateAsync(user, cancellationToken);

                string displayName = user.UserName ?? GetDisplayName(user);
                return new Tuple<AccountLoginResults, Account?>(AccountLoginResults.Success,
                    new Account($"{user.Id}", $"{user.OrgId}", user.LoginName, displayName));
            }
            else
            {
                // 登录失败
                user.LoginFail();

                // 连续登录失败次数超过上限则锁定账号
                if (_upmsOptions.FailedPasswordAllowedAttempts > 0 &&
                    user.FailedLoginAttempts > _upmsOptions.FailedPasswordAllowedAttempts)
                {
                    user.LoginLock(DateTime.UtcNow.AddMinutes(_upmsOptions.FailedPasswordLockoutMinutes));
                }

                await _userRepository.UpdateAsync(user, cancellationToken);
                return new Tuple<AccountLoginResults, Account?>(AccountLoginResults.WrongPassword, null);
            }
        }

        private static string GetDisplayName(User user)
        {
            if (user == null)
                return string.Empty;

            if (user.IsPhoneVerified && !string.IsNullOrEmpty(user.UserPhone))
                return StringHelper.MaskPhone(user.UserPhone);  // 如果手机号码已验证，直接使用手机号码作为显示名称

            if (user.IsEmailVerified && !string.IsNullOrEmpty(user.UserEmail))
                return StringHelper.MaskEmail(user.UserEmail);  // 如果邮箱已验证，直接使用邮箱作为显示名称

            return user.LoginName;  // 否则使用登录名作为显示名称
        }

        /// <summary>
        /// 校验指定用户的密码是否正确
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="password"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task<DomainRuleCheckResult> ValidateAccountPasswordAsync(string userId, string password, CancellationToken cancellationToken = default)
        {
            if (string.IsNullOrEmpty(password))
                return DomainRuleCheckResult.Fail("未输入密码");

            if (!long.TryParse(userId, out long id))
                return DomainRuleCheckResult.Fail("无法获取当前用户信息，请确保用户已登录。");

            User? user = await _userRepository.GetAsync(id, cancellationToken);
            if (user == null)
                return DomainRuleCheckResult.Fail("用户不存在");

            bool passwordValid = user.CheckPassword(_passwordHashService, password);
            var result = await ProcessAccountValidationAsync(user, passwordValid, cancellationToken);

            switch (result.Item1)
            {
                case AccountLoginResults.Success:
                    return DomainRuleCheckResult.Ok();

                case AccountLoginResults.IsDelete:
                    return DomainRuleCheckResult.Fail("账号已被冻结");

                case AccountLoginResults.IsDeactivate:
                    return DomainRuleCheckResult.Fail("账号已被停用");

                case AccountLoginResults.IsLock:
                    return DomainRuleCheckResult.Fail("账号已被锁定");

                case AccountLoginResults.WrongPassword:
                    {
                        var remainingAttempts = _upmsOptions.FailedPasswordAllowedAttempts - user.FailedLoginAttempts;
                        if (remainingAttempts > 0)
                            return DomainRuleCheckResult.Fail($"密码错误（密码区分大小写），还剩 {remainingAttempts} 次尝试机会");
                        else
                            return DomainRuleCheckResult.Fail($"密码错误次数过多，账号将被锁定 {_upmsOptions.FailedPasswordLockoutMinutes} 分钟");
                    }
                default:
                    return DomainRuleCheckResult.Fail("账号状态异常");
            }
        }

        public async Task<Tuple<ValidateRefreshTokenResults, Account?>> ValidateRefreshTokenAsync(string userId, string refreshToken, CancellationToken cancellationToken = default)
        {
            if (string.IsNullOrEmpty(refreshToken))
                return new Tuple<ValidateRefreshTokenResults, Account?>(ValidateRefreshTokenResults.NotExist, null);

            UserRefreshToken? userRefreshToken = await _userRefreshTokenRepository.GetByRefreshTokenAsync(refreshToken, cancellationToken);

            if (userRefreshToken == null)
                return new Tuple<ValidateRefreshTokenResults, Account?>(ValidateRefreshTokenResults.NotExist, null);

            if (userRefreshToken.IsExpire())
                return new Tuple<ValidateRefreshTokenResults, Account?>(ValidateRefreshTokenResults.Expire, null);

            if (!long.TryParse(userId, out long id) || userRefreshToken.UserId != id)
                return new Tuple<ValidateRefreshTokenResults, Account?>(ValidateRefreshTokenResults.NotExist, null);

            if (userRefreshToken.GetRefreshInterval() < _upmsOptions.RefreshTokenDelaySecond)
                return new Tuple<ValidateRefreshTokenResults, Account?>(ValidateRefreshTokenResults.Frequently, null);

            User? user = await _userRepository.GetAsync(id, cancellationToken);

            if (user == null || user.GetUserState() != UserState.Normal)
                return new Tuple<ValidateRefreshTokenResults, Account?>(ValidateRefreshTokenResults.AccountAbnormal, null);

            string displayName = user.UserName ?? GetDisplayName(user);
            return new Tuple<ValidateRefreshTokenResults, Account?>(ValidateRefreshTokenResults.Success,
                 new Account($"{user.Id}", $"{user.OrgId}", user.LoginName, displayName));
        }

        /// <summary>
        /// 移除刷新令牌
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="refreshToken"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task RemoveRefreshTokenAsync(string userId, string refreshToken, CancellationToken cancellationToken = default)
        {
            if (!long.TryParse(userId, out long id))
                return;

            UserRefreshToken? userRefreshToken = await _userRefreshTokenRepository.GetByRefreshTokenAsync(refreshToken, cancellationToken);
            if (userRefreshToken != null && userRefreshToken.UserId == id)
                await _userRefreshTokenRepository.DeleteAsync(userRefreshToken, cancellationToken);
        }

        public async Task CreateLoginLogAsync(
            string userId,
            string loginName,
            string displayName,
            string ipAddress,
            AccountLoginWay loginWay,
            string clientBrowser,
            string clientOs,
            bool status,
            string remark,
            string orgId,
            CancellationToken cancellationToken = default)
        {
            if (!long.TryParse(userId, out long uid))
                uid = 0;

            if (!long.TryParse(orgId, out long oid))
                oid = 0;

            LogLogin logLogin = new LogLogin(loginName, oid)
            {
                ClientOs = clientOs,
                IpAddress = ipAddress,
                LoginWay = loginWay,
                LoginTime = DateTime.UtcNow,
                DisplayName = displayName,
                Status = status,
                Remark = remark,
                ClientBrowser = clientBrowser,
                CreatorUserId = uid
            };
            await _logLoginRepository.InsertAsync(logLogin, cancellationToken);
        }
    }
}